package com.ev.scm.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.ev.asl.domain.AslBarCodeDO;
import com.ev.asl.domain.AslBarCodeTrayDO;
import com.ev.asl.service.AslBarCodeService;
import com.ev.asl.service.KisBillOfPushService;
import com.ev.custom.domain.DictionaryDO;
import com.ev.custom.service.DictionaryService;
import com.ev.custom.service.MaterielService;
import com.ev.custom.vo.StockInItemVO;
import com.ev.custom.vo.StockInVO;
import com.ev.custom.vo.StockOutVO;
import com.ev.framework.config.Constant;
import com.ev.framework.config.ConstantForGYL;
import com.ev.framework.exception.BDException;
import com.ev.framework.il8n.MessageSourceHandler;
import com.ev.framework.utils.*;
import com.ev.scm.dao.AslBarStockDao;
import com.ev.scm.dao.StockOutDao;
import com.ev.scm.domain.*;
import com.ev.scm.service.*;
import com.ev.scm.vo.LogisticsVO;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Service
@Primary
@Slf4j
public class StockOutServiceImpl implements StockOutService {
    @Autowired
    private StockOutDao stockOutDao;
    @Autowired
    private StockOutItemService stockOutItemService;
    @Autowired
    private MaterielService materielService;
    @Autowired
    private MessageSourceHandler messageSourceHandler;
    @Autowired
    private QrcodeServiceImpl qrcodeService;
    @Autowired
    private StockService stockService;
    @Autowired
    private AslBarStockDao aslBarStockDao;
    @Autowired
    private IDeliveryNoteService deliveryNoteService;

    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    private AslBarCodeService aslBarCodeService;
    @Autowired
    private DictionaryService dictionaryService;
    @Autowired
    private StockItemService stockItemService;
    @Autowired
    private LogisticsService logisticsService;
    @Autowired
    private ConsumingStockOutService consumingStockOutService;
    @Autowired
    private KisBillOfPushService kisBillOfPushService;

    @Override
    public StockOutDO get(Long id) {
        return stockOutDao.get(id);
    }

    @Override
    public List<StockOutDO> list(Map<String, Object> map) {
        return stockOutDao.list(map);
    }

    @Override
    public int count(Map<String, Object> map) {
        return stockOutDao.count(map);
    }

    @Override
    public int save(StockOutDO stockOutDO) {
        return stockOutDao.save(stockOutDO);
    }

    @Override
    public int update(StockOutDO stockOutDO) {
        return stockOutDao.update(stockOutDO);
    }

    @Override
    public int updateAll(StockOutDO stockOut){
        return stockOutDao.updateAll(stockOut);
    }

    @Override
    public int remove(Long id, Long outType) {
        this.stockOutItemService.removeByStockOutId(id);
        this.removeStockDetail(id, outType);
        return this.stockOutDao.remove(id);
    }

    @Override
    public int batchRemove(Long[] ids, Long outType) {
        int count = stockOutDao.batchRemove(ids);
        stockOutItemService.batchRemoveByStockOutId(ids);
        // 只有在待审核状态下才可以删除库存明细表数据
        for (Long id : ids) {
            this.removeStockDetail(id, outType);
        }
        return count;
    }

    @Override
    public List<Long> getAllStockIds(List<Map<String, Object>> params) {
        // 获取库存明细表中保存的库位id数组
        List<Long> stockIds = new ArrayList<>();
        StringBuilder buffer = new StringBuilder();
        int log = 0;
        for (Map<String, Object> stockId : params) {
            log++;
            buffer.append(stockId.get("id"));
            if (log == params.size()) {
                break;
            }
            buffer.append(",");
        }
        String stockIdString = new String(buffer);
        if (stockIdString.contains(",")) {
            String[] stockStrings = stockIdString.split(",");
            for (String string : stockStrings) {
                stockIds.add(Long.parseLong(string));
            }
        }
        if (!stockIdString.contains(",")) {
            stockIds.add(Long.parseLong(stockIdString));
        }
        return stockIds;
    }


    @Override
    public R add(StockOutDO stockOutDO, String item, DictionaryDO storageType) {
        Map<String, Object> result = Maps.newHashMap();
        List<StockOutItemDO> itemDOs = JSON.parseArray(item, StockOutItemDO.class);
        itemDOs.forEach(e->{
            if (e.getAmount()==null){
                e.setAmount(BigDecimal.ZERO);
            }
            if (e.getUnitPrice()==null){
                e.setUnitPrice(BigDecimal.ZERO);
            }
        });

        // 是否为二维码出库
        if (itemDOs.get(0).getQrcodeId() != null) {
            return this.addByQrcodeId(stockOutDO,itemDOs,storageType);
        }

        // 将JSON 解析出来以库存ID 数组 和 数量作为验证条件
        List<Map<String, Object>> params = this.stockListParam(itemDOs);
        // 获取库存明细表中保存的库位id数组
        List<Long> stockIds = getAllStockIds(params);
        if (checkInsert(params, stockIds)) {
            return R.error(messageSourceHandler.getMessage("stock.count.error", null));
        }

        // 库存无误执行下面保存操作
        List<StockDO> stockList = materielService.stockList(stockIds);
        // 设置出库类型
        long storageTypeId = storageType.getId();
        stockOutDO.setOutboundType(storageTypeId);
        // 设置审核状态为待审核
        stockOutDO.setAuditSign(Constant.WAIT_AUDIT);
        // 设置出库单据号
        String value = storageType.getValue();

        //获取编号
        String maxNo = DateFormatUtil.getWorkOrderno(value);
        Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
        param.put("maxNo", maxNo);
        param.put("offset", 0);
        param.put("limit", 1);
        List<StockOutDO> list = this.list(param);
        String taskNo = null;
        if (list.size() > 0) {
            taskNo = list.get(0).getOutCode();
        }
        stockOutDO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));

        // 保存主表数据
        int count = this.stockOutDao.save(stockOutDO);
        Long stockOutId = stockOutDO.getId();
        result.put("id", stockOutId);

        if (count > 0) {
            // 保存出库产品
            List<StockOutItemDO> saveItemDOs = Lists.newArrayList();
            for (StockOutItemDO obj : itemDOs) {
                obj.setOutId(stockOutDO.getId());
                saveItemDOs.add(obj);
            }
            // 批量更新子表数据
            int saveItemCount = stockOutItemService.batchInsert(saveItemDOs);
            // 子表数据保存成功保存库存信息
            if (saveItemCount == saveItemDOs.size()) {
                List<Pair<List<StockItemDO>, List<StockDO>>> stockInfos = Lists.newArrayList();
                for (StockOutItemDO itemDO : itemDOs) {
                    BigDecimal proCount = itemDO.getCount();
                    // 保存库存明细数据并设置为未生效
                    Pair<List<StockItemDO>, List<StockDO>> saveStockDetail = this.saveStockDetail(stockOutId, itemDO.getId(),
                            itemDO.getStockId(), proCount, itemDO.getSourceType(), storageTypeId,
                            stockList);
                    if (saveStockDetail != null) {
                        stockInfos.add(saveStockDetail);
                    }
                }
                // 保存库存明细
                this.batchSaveStockInfo(stockInfos);
                return R.ok(result);
            }
        }
        return R.error();
    }

    @Override
    public R addByQrcodeId(StockOutDO stockOutDO, List<StockOutItemDO> itemDOs, DictionaryDO storageType) {
        // 检查二维码中的数量还足不足
        Map<String,Object> params ;
        for (StockOutItemDO itemDO : itemDOs) {
            params = Maps.newHashMap();
            params.put("id",itemDO.getQrcodeId());
            params.put("remainCount",itemDO.getCount());
            List<QrcodeDO> errorList = qrcodeService.list(params);
            if (errorList.size() > 0) {
                return R.error(messageSourceHandler.getMessage("scm.stock.code.outError", null));
            }
        }

        // 设置出库类型
        long storageTypeId = storageType.getId();
        stockOutDO.setOutboundType(storageTypeId);
        // 设置审核状态为待审核
        stockOutDO.setAuditSign(Constant.OK_AUDITED);
        Date now = new Date();
        stockOutDO.setOutTime(now);
        stockOutDO.setAuditTime(now);
        stockOutDO.setAuditor(ShiroUtils.getUserId());
        // 设置出库单据号
        String value = storageType.getValue();

        //获取编号
        String maxNo = DateFormatUtil.getWorkOrderno(value);
        Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
        param.put("maxNo", maxNo);
        param.put("offset", 0);
        param.put("limit", 1);
        List<StockOutDO> list = this.list(param);
        String taskNo = null;
        if (list.size() > 0) {
            taskNo = list.get(0).getOutCode();
        }
        stockOutDO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));

        // 保存主表数据
        this.stockOutDao.save(stockOutDO);
        Long stockOutId = stockOutDO.getId();
        Map<String, String> materielIdAndBatchForStockId = Maps.newHashMap();
        Map<String, BigDecimal> materielIdAndBatchForCount = Maps.newHashMap();
        List<StockOutItemDO> insertStockOutItemDOs = Lists.newArrayList();
        List<StockDO> updateStockDOList = Lists.newArrayList();
        List<StockItemDO> insertStockItemDOList = Lists.newArrayList();
        String batch;
        StockDO stockDO;
        StockItemDO stockItemDO;
        for (StockOutItemDO itemDO : itemDOs) {
            String stockId = itemDO.getStockId();
            BigDecimal count = itemDO.getCount();
            batch = itemDO.getBatch();
            batch = batch == null ? "" : batch;

            long stockIdLong = Long.parseLong(stockId);
            stockDO = stockService.get(stockIdLong);
            stockDO.setCount(stockDO.getCount().subtract(count));
            stockDO.setAvailableCount(stockDO.getAvailableCount().subtract(count));
            updateStockDOList.add(stockDO);

            stockItemDO = new StockItemDO();
            stockItemDO.setStockId(stockIdLong);
            stockItemDO.setUnitPrice(itemDO.getUnitPrice());
            stockItemDO.setCount(count.multiply(new BigDecimal(-1)));
            stockItemDO.setInOutType(storageTypeId);
            stockItemDO.setSourceType(itemDO.getSourceType());
            stockItemDO.setHandleSign(1L);
            insertStockItemDOList.add(stockItemDO);

            String materielIdAndBatch = itemDO.getMaterielId().toString() + "&" + batch;
            if (materielIdAndBatchForStockId.containsKey(materielIdAndBatch)) {
                materielIdAndBatchForStockId.put(materielIdAndBatch, materielIdAndBatchForStockId.get(materielIdAndBatch)+","+stockId);
                materielIdAndBatchForCount.put(materielIdAndBatch,materielIdAndBatchForCount.get(materielIdAndBatch).add(count));
                continue;
            }
            materielIdAndBatchForCount.put(materielIdAndBatch,count);
            materielIdAndBatchForStockId.put(materielIdAndBatch,stockId);
        }
        StockOutItemDO stockOutItemDO;
        for (String s : materielIdAndBatchForStockId.keySet()) {
            for (StockOutItemDO itemDO : itemDOs) {
                batch = itemDO.getBatch();
                batch = batch == null ? "" : batch;
                String materielIdAndBatch = itemDO.getMaterielId().toString() + "&" + batch;
                if (s.equals(materielIdAndBatch)) {
                    stockOutItemDO = new StockOutItemDO();
                    BeanUtils.copyProperties(itemDO,stockOutItemDO);
                    stockOutItemDO.setStockId(materielIdAndBatchForStockId.get(s));
                    stockOutItemDO.setCount(materielIdAndBatchForCount.get(s));
                    stockOutItemDO.setOutId(stockOutId);
                    insertStockOutItemDOs.add(stockOutItemDO);
                    break;
                }
            }
        }
        stockOutItemService.batchInsert(insertStockOutItemDOs);
        stockService.batchUpdate(updateStockDOList);
        this.batchInsertStockDetailDO(insertStockItemDOList);
        qrcodeService.saveOutQrCode(insertStockOutItemDOs,itemDOs);

        return R.ok();
    }
    @Override
    public R addByBarcodeId(StockOutDO stockOutDO, List<StockOutItemDO> itemDOs, StockDO stockDO, DictionaryDO storageType) {
        // 检查二维码中的数量还足不足
//        Map<String,Object> params ;
//        for (StockOutItemDO itemDO : itemDOs) {
//            params = Maps.newHashMap();
//            params.put("id",itemDO.getQrcodeId());
//            params.put("remainCount",itemDO.getCount());
//            List<QrcodeDO> errorList = qrcodeService.list(params);
//            if (errorList.size() > 0) {
//                return R.error(messageSourceHandler.getMessage("scm.stock.code.outError", null));
//            }
//        }

        // 设置出库类型
        long storageTypeId = storageType.getId();
        stockOutDO.setOutboundType(storageTypeId);
        // 设置审核状态为已审核
        stockOutDO.setAuditSign(Constant.OK_AUDITED);
        Date now = new Date();
        stockOutDO.setOutTime(now);
        stockOutDO.setAuditTime(now);
        stockOutDO.setAuditor(ShiroUtils.getUserId());
        // 设置出库单据号
        String value = storageType.getValue();

        //获取编号
        String maxNo = DateFormatUtil.getWorkOrderno(value);
        Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
        param.put("maxNo", maxNo);
        param.put("offset", 0);
        param.put("limit", 1);
        List<StockOutDO> list = this.list(param);
        String taskNo = null;
        if (list.size() > 0) {
            taskNo = list.get(0).getOutCode();
        }
        stockOutDO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));

        // 保存主表数据
        this.stockOutDao.save(stockOutDO);
        Long stockOutId = stockOutDO.getId();
        Map<String, String> materielIdAndBatchForStockId = Maps.newHashMap();
        Map<String, BigDecimal> materielIdAndBatchForCount = Maps.newHashMap();
        List<StockOutItemDO> insertStockOutItemDOs = Lists.newArrayList();
        List<StockDO> updateStockDOList = Lists.newArrayList();
        List<StockItemDO> insertStockItemDOList = Lists.newArrayList();
        String batch;
        StockItemDO stockItemDO;
        for (StockOutItemDO itemDO : itemDOs) {
            BigDecimal count = itemDO.getCount();
            batch = itemDO.getBatch();
            batch = batch == null ? "" : batch;


            stockDO.setCount(stockDO.getCount().subtract(count));
            stockDO.setAvailableCount(stockDO.getAvailableCount().subtract(count));
            updateStockDOList.add(stockDO);

            stockItemDO = new StockItemDO();
            stockItemDO.setStockId(stockDO.getId());
            stockItemDO.setUnitPrice(itemDO.getUnitPrice());
            stockItemDO.setCount(count.multiply(new BigDecimal(-1)));
            stockItemDO.setInOutType(storageTypeId);
            stockItemDO.setSourceType(itemDO.getSourceType());
            stockItemDO.setHandleSign(1L);
            insertStockItemDOList.add(stockItemDO);

            String materielIdAndBatch = itemDO.getMaterielId().toString() + "&" + batch;
            if (materielIdAndBatchForStockId.containsKey(materielIdAndBatch)) {
                materielIdAndBatchForStockId.put(materielIdAndBatch, materielIdAndBatchForStockId.get(materielIdAndBatch)+","+stockDO.getId());
                materielIdAndBatchForCount.put(materielIdAndBatch,materielIdAndBatchForCount.get(materielIdAndBatch).add(count));
                continue;
            }
            materielIdAndBatchForCount.put(materielIdAndBatch,count);
            materielIdAndBatchForStockId.put(materielIdAndBatch,stockDO.getId().toString());
        }
        StockOutItemDO stockOutItemDO;
        for (String s : materielIdAndBatchForStockId.keySet()) {
            for (StockOutItemDO itemDO : itemDOs) {
                batch = itemDO.getBatch();
                batch = batch == null ? "" : batch;
                String materielIdAndBatch = itemDO.getMaterielId().toString() + "&" + batch;
                if (s.equals(materielIdAndBatch)) {
                    stockOutItemDO = new StockOutItemDO();
                    BeanUtils.copyProperties(itemDO,stockOutItemDO);
                    stockOutItemDO.setStockId(materielIdAndBatchForStockId.get(s));
                    stockOutItemDO.setCount(materielIdAndBatchForCount.get(s));
                    stockOutItemDO.setOutId(stockOutId);
                    insertStockOutItemDOs.add(stockOutItemDO);
                    break;
                }
            }
        }
        stockOutItemService.batchInsert(insertStockOutItemDOs);
        stockService.batchUpdate(updateStockDOList);
        this.batchInsertStockDetailDO(insertStockItemDOList);
//        qrcodeService.saveOutQrCode(insertStockOutItemDOs,itemDOs);
        //反写投料单
        retrographyAndAudit(stockOutId,itemDOs,storageType.getId().intValue(),false);
        return R.ok();
    }

    @Override
    public void batchSaveStockInfo(List<Pair<List<StockItemDO>, List<StockDO>>> stockInfos) {
        if (stockInfos.size() > 0) {
            List<StockDO> batchUpdateStockDO = new ArrayList<>();
            List<StockItemDO> batchInsertStockDetailDO = new ArrayList<>();
            for (Pair<List<StockItemDO>, List<StockDO>> stockInfo : stockInfos) {
                batchUpdateStockDO.addAll(stockInfo.getRight());
                batchInsertStockDetailDO.addAll(stockInfo.getLeft());
            }
            this.batchUpdateStockDO(batchUpdateStockDO);
            this.batchInsertStockDetailDO(batchInsertStockDetailDO);
        }
    }

    @Override
    public R batchDelete(Long[] ids, Long outType) {
        for (Long id : ids) {
            StockOutDO stockOutDO = this.get(id);
            if (this.isQrcode(id)) {
                return R.error(messageSourceHandler.getMessage("scm.stock.code.operateError", null));
            }

            if (Objects.equal(stockOutDO.getAuditSign(), Constant.OK_AUDITED)) {
                return R.error(messageSourceHandler.getMessage("common.approvedOrChild.delete.disabled", null));
            }
        }
        return this.batchRemove(ids, outType) > 0 ? R.ok() : R.error();
    }

    @Override
    public R reverseAuditForR(Long id, Long outType) {
        StockOutDO stockOutDO = get(id);
        if (this.isQrcode(id)) {
            return R.error(messageSourceHandler.getMessage("scm.stock.code.operateError", null));
        }
        Date periodTime = stockService.getPeriodTime();
        if (periodTime == null) {
            return R.error(messageSourceHandler.getMessage("scm.stock.nonUse", null));
        }

        if (stockOutDO.getOutTime().before(periodTime)) {
//            String[] args = {DateFormatUtil.getFormateDate(periodTime)};
            return R.error(messageSourceHandler.getMessage("scm.operate.isCarryOver", null));
        }
        
        Long auditSignId = stockOutDO.getAuditSign();
        int count = 0;
        // 若是已审核状态设置审核状态为反审核
        if (auditSignId.equals(Constant.OK_AUDITED)) {
            stockOutDO.setAuditSign(Constant.WAIT_AUDIT);
            // 设置审核人
            stockOutDO.setAuditor(null);
            stockOutDO.setAuditTime(null);
            // 修改出库单据状态
            count = this.updateAll(stockOutDO);
            // 操作库存数据
            this.reverseAudit(id, outType);
        }
        return count > 0 ? null : R.error(messageSourceHandler.getMessage("common.massge.faildRollBackAudit", null));
    }

    @Override
    public Map<String, Object> countTotal(Map<String, Object> params) {
        return stockOutDao.countTotal(params);
    }

    @Override
    public int childCount(Long id) {
        return stockOutDao.childCount(id);
    }

    @Override
    public boolean isQrcode(Long id) {
        Map<String,Object> map = Maps.newHashMap();
        map.put("outId",id);
        List<StockOutItemDO> list = stockOutItemService.list(map);
        if (list.size() > 0) {
            return list.get(0).getQrcodeId() != null;
        }
        return true;
    }

    @Override
    public R checkSourceNumber(String item,Long id) {
        return null;
    }

    @Override
    public void batchInsertStockDetailDO(List<StockItemDO> batchInsertStockDetailDO) {
        stockOutDao.batchInsertStockDetailDO(batchInsertStockDetailDO);
    }

    @Override
    public void batchUpdateStockDO(List<StockDO> batchUpdateStockDO) {
        stockOutDao.batchUpdateStockDO(batchUpdateStockDO);
    }

    private List<Map<String, Object>> stockListParam(List<StockOutItemDO> itemDOs) {
        List<Map<String, Object>> params = new ArrayList<>();
        Map<String, Object> param;
        for (StockOutItemDO itemDO : itemDOs) {
            param = Maps.newHashMapWithExpectedSize(2);
            param.put("id", itemDO.getStockId());
            param.put("outCount", itemDO.getCount());
            params.add(param);
        }
        return params;
    }

    @Override
    public boolean checkInsert(List<Map<String, Object>> params, List<Long> stockIds) {
        Map<String, Object> param = Maps.newHashMap();
        param.put("stockId", stockIds);
        // 获取即将出库产品实际数量
        List<Map<String, Object>> stockListForMap = materielService.stockCount(param);
        // 将相同库存ID的数据当作一类数据统计
        Map<String,BigDecimal> stringBigDecimalMap = Maps.newHashMap();
        for (Map<String, Object> map : params) {
            String stockId = map.get("id").toString();
            if (stringBigDecimalMap.containsKey(stockId)) {
                stringBigDecimalMap.put(stockId,stringBigDecimalMap.get(stockId).add(MathUtils.getBigDecimal(map.get("outCount"))));
                continue;
            }
            stringBigDecimalMap.put(stockId,MathUtils.getBigDecimal(map.get("outCount")));
        }

        for (Map<String, Object> stockCount : stockListForMap) {
            String stockId = stockCount.get("id").toString();
            if (stringBigDecimalMap.containsKey(stockId)) {
                if (MathUtils.getBigDecimal(stockCount.get("availableCount"))
                        .compareTo(stringBigDecimalMap.get(stockId)) < 0) {
                    return true;
                }
            }

//            for (Map<String, Object> outCount : params) {
//                if (outCount.get("id").toString().equals(stockCount.get("id").toString())) {
//                    if (MathUtils.getBigDecimal(stockCount.get("availableCount"))
//                            .compareTo(MathUtils.getBigDecimal(outCount.get("outCount"))) < 0) {
//                        return true;
//                    }
//                }
//            }
        }
        return false;
    }

    @Override
    public R audit(Long id, Long outType) {
        StockOutDO stockOutDO = get(id);
        Date periodTime = stockService.getPeriodTime();
        if (periodTime == null) {
            return R.error(messageSourceHandler.getMessage("scm.stock.nonUse", null));
        }
        if (stockOutDO.getOutTime().before(periodTime)) {
//            String[] args = {DateFormatUtil.getFormateDate(periodTime)};
            return R.error(messageSourceHandler.getMessage("scm.operate.isCarryOver", null));
        }

        Long auditSignId = stockOutDO.getAuditSign();
        int count = 0;
        // 若是待审核状态设置审核状态为已审核
        if (auditSignId.equals(Constant.WAIT_AUDIT)) {
            stockOutDO.setAuditSign(Constant.OK_AUDITED);
            // 设置审核时间
            stockOutDO.setAuditTime(new Date());
            // 设置审核人
            stockOutDO.setAuditor(ShiroUtils.getUserId());
            count = update(stockOutDO);
            // 保存库存明细
            this.insertStockInfo(id, outType);
        }
        return count > 0 ? null : R.error(messageSourceHandler.getMessage("common.duplicate.approved", null));
    }

    @Override
    public void insertStockInfo(Long id, Long outType) {
        Map<String, Object> param = Maps.newHashMap();
        param.put("inheadId", id);
        param.put("inOutType", outType);
        List<StockItemDO> stockDetailDO = stockItemService.list(param);

        param.put("handleSign", 1);
        stockOutDao.updateStockDetail(param);
        // 获取所有的库存明细变化
        if (stockDetailDO.size() > 0) {
            // 批量更新库存明细数据集合
            List<StockDO> stockDOs = Lists.newArrayList();
            List<Long> stockIds = Lists.newArrayList();
            for (StockItemDO stockDetail : stockDetailDO) {
                // 修改库存表里数据
                stockIds.add(stockDetail.getStockId());
            }
            List<StockDO> stockList = materielService.stockList(stockIds);
            for (StockDO stockDO : stockList) {
                for (StockItemDO stockDetail : stockDetailDO) {
                    if (stockDetail.getStockId().equals(stockDO.getId())) {
                        stockDO.setCount(stockDO.getCount().add(stockDetail.getCount()));
                        stockDOs.add(stockDO);
                    }
                }
            }
            batchUpdateStockDO(stockDOs);
        }
    }

    @Override
    public Pair<List<StockItemDO>, List<StockDO>> saveStockDetail(Long stockOutId, Long stockOutItemId, String stockId, BigDecimal proCount,
                                                                    Long sourceType, Long storageType, List<StockDO> stockList) {
        // 返回结果集
        List<Long> stockIds = getStockIds(stockId);
        // 批量保存集合
        List<StockItemDO> stockDetailDOs = Lists.newArrayList();
        // 批量修改集合
        List<StockDO> stockDOs = Lists.newArrayList();
        StockItemDO detailDO;
        for (StockDO stockDO : stockList) {
            if (stockIds.contains(stockDO.getId())) {
                detailDO = new StockItemDO();
                // 库存可用数量
                BigDecimal count = stockDO.getAvailableCount();
                // 库存产品数量大于或等于出库明细表数量
                if (count.compareTo(proCount) > 0 || count.compareTo(proCount) == 0) {
                    // 修改库存表可用数量
                    stockDO.setAvailableCount(count.subtract(proCount));
                    // 将需要更改库存数据保存进集合中批量更改
                    stockDOs.add(stockDO);
                    // 添加库存明细信息
                    this.saveStockDetailInfo(detailDO, stockOutId, stockOutItemId, stockDO,
                            BigDecimal.ZERO.subtract(proCount), storageType, sourceType);
                    // 将需要更改库存明细数据保存进集合中批量保存
                    stockDetailDOs.add(detailDO);
                    // 将需要更改的数据保存入集合中
                    return Pair.of(stockDetailDOs, stockDOs);
                }
                // 若库存产品数量小于出库明细数量
                if (count.compareTo(proCount) < 0) {
                    if (count.compareTo(BigDecimal.ZERO) > 0) {
                        // 设置该库存的可用数量为0
                        stockDO.setAvailableCount(BigDecimal.ZERO);
                        // 添加库存明细信息
                        // 将需要更改库存数据保存进集合中批量更改
                        stockDOs.add(stockDO);
                        this.saveStockDetailInfo(detailDO, stockOutId, stockOutItemId, stockDO,
                                BigDecimal.ZERO.subtract(count), storageType, sourceType);
                        // 将需要更改库存明细数据保存进集合中批量保存
                        stockDetailDOs.add(detailDO);
                        proCount = proCount.subtract(count);
                    }
                }
            }
        }
        return null;
    }

    @Override
    public List<Long> getStockIds(String stockId) {
        List<Long> stockIds = new ArrayList<>();
        if (stockId.contains(",")) {
            String[] split = stockId.split(",");
            for (String string : split) {
                stockIds.add(Long.parseLong(string));
            }
        }
        if (!stockId.contains(",")) {
            stockIds.add(Long.parseLong(stockId));
        }
        return stockIds;
    }

    @Override
    public void saveStockDetailInfo(StockItemDO stockDetail, Long stockOutId, Long stockOutItemId, StockDO stockDO, BigDecimal change,
                                    Long storageType, Long sourceType) {
        stockDetail.setInbodyId(stockOutItemId);
        stockDetail.setStockId(stockDO.getId());
        stockDetail.setInheadId(stockOutId);
        stockDetail.setUnitPrice(stockDO.getUnitPrice());
        stockDetail.setCount(change);
        stockDetail.setInOutType(storageType);
        stockDetail.setSourceType(sourceType);
        // 设置为未生效状态
        if (change.compareTo(BigDecimal.ZERO) > 0) {
            stockDetail.setHandleSign(1L);
        }
        if (change.compareTo(BigDecimal.ZERO) < 0) {
            stockDetail.setHandleSign(0L);
        }
    }

    @Override
    public void reverseAudit(Long id, Long outType) {
        Map<String, Object> param = Maps.newHashMap();
        param.put("inheadId", id);
        param.put("inOutType", outType);
        List<StockItemDO> stockDetailDO = stockItemService.list(param);

        param.put("handleSign", 0);
        stockOutDao.updateStockDetail(param);
        // 获取所有的库存明细变化

        List<Long> removeStockIds = Lists.newArrayList();
        List<Long> stockIds = Lists.newArrayList();
        List<StockDO> batchUpdateStockDO = Lists.newArrayList();
        for (StockItemDO stockDetail : stockDetailDO) {
            BigDecimal stockDetailCount = stockDetail.getCount();
            // 调拨
            if (stockDetailCount.compareTo(BigDecimal.ZERO) > 0) {
                removeStockIds.add(stockDetail.getStockId());
            }
            // 若是库存明细表中数量为负数则是出库操作，则进行修改操作将已经出库的操作撤回
            if (stockDetailCount.compareTo(BigDecimal.ZERO) < 0) {
                stockIds.add(stockDetail.getStockId());
            }
        }
        if (removeStockIds.size() > 0) {
            stockService.batchRemove(removeStockIds.toArray(new Long[0]));
        }
        List<StockDO> stockList = materielService.stockList(stockIds);
        for (StockItemDO stockDetail : stockDetailDO) {
            BigDecimal detailCount = stockDetail.getCount();
            if (detailCount.compareTo(BigDecimal.ZERO) < 0) {
                for (StockDO stockDO : stockList) {
                    if (stockDetail.getStockId().equals(stockDO.getId())) {
                        stockDO.setCount(stockDO.getCount().add(detailCount.multiply(new BigDecimal(-1))));
//                        stockDO.setAvailableCount(
//                                stockDO.getAvailableCount().add(detailCount.multiply(new BigDecimal(-1))));
                        batchUpdateStockDO.add(stockDO);
                        break;
                    }
                }
            }
        }
        stockOutDao.batchUpdateStockDO(batchUpdateStockDO);
        // 删除库存明细表里的数据
//        stockItemService.removeByInheadId(outType, id);
    }

    @Override
    public void removeStockDetail(Long id, Long outType) {
        Map<String, Object> param = Maps.newHashMap();
        param.put("inheadId", id);
        param.put("inOutType", outType);
        // 获取所有的库存明细变化
        List<StockItemDO> stockDetailDO = stockItemService.list(param);
        if (stockDetailDO.size() == 0) {
            return;
        }
        List<Long> stockIds = Lists.newArrayList();
        for (StockItemDO stockDetail : stockDetailDO) {
            // 若是库存明细表中数量为负数则是出库操作，则进行修改操作将已经出库的操作撤回
            if (stockDetail.getCount().compareTo(BigDecimal.ZERO) < 0) {
                stockIds.add(stockDetail.getStockId());
            }
        }
        List<StockDO> stockList = materielService.stockList(stockIds);
        List<StockDO> batchUpdateStockDO = Lists.newArrayList();
        for (StockItemDO stockDetail : stockDetailDO) {
            BigDecimal detailCount = stockDetail.getCount();
            if (detailCount.compareTo(BigDecimal.ZERO) < 0) {
                for (StockDO stockDO : stockList) {
                    if (stockDetail.getStockId().equals(stockDO.getId())) {
                        stockDO.setAvailableCount(
                                stockDO.getAvailableCount().add(detailCount.multiply(new BigDecimal(-1))));
                        batchUpdateStockDO.add(stockDO);
                    }
                }
            }
        }
        stockOutDao.batchUpdateStockDO(batchUpdateStockDO);
        this.stockItemService.removeByInheadId(outType, id);
    }

    @Override
    public R edit(StockOutDO stockOutDO, String item, Long storageType,Long [] itemIds) {
        StockOutDO stockOut = this.get(stockOutDO.getId());
        if (Objects.equal(stockOut.getAuditSign(),Constant.OK_AUDITED)) {
            return R.error(messageSourceHandler.getMessage("common.approved.update.disabled", null));
        }
        // 修改前先删除库存记录
        this.removeStockDetail(stockOutDO.getId(), storageType);
        // 修改出库产品
        List<StockOutItemDO> itemDOs = JSON.parseArray(item, StockOutItemDO.class);
        itemDOs.forEach(e->{
            if (e.getAmount()==null){
                e.setAmount(BigDecimal.ZERO);
            }
            if (e.getUnitPrice()==null){
                e.setUnitPrice(BigDecimal.ZERO);
            }
        });
        List<Map<String, Object>> stockListParam = this.stockListParam(itemDOs);
        List<Long> stockIds = getAllStockIds(stockListParam);
        if (checkInsert(stockListParam, stockIds)) {
            return R.error(messageSourceHandler.getMessage("stock.count.error", null));
        }

        int count = this.stockOutDao.update(stockOutDO);
        if (count > 0) {
            if(itemIds.length>0){
                stockOutItemService.batchRemove(itemIds);
            }

            List<StockOutItemDO> updateItemDOs = new ArrayList<>();
            // 库存无误执行下面修改操作
            List<StockDO> stockList = materielService.stockList(stockIds);
            List<Pair<List<StockItemDO>, List<StockDO>>> stockInfos = Lists.newArrayList();
            for (StockOutItemDO itemDO : itemDOs) {
                Long stockOutDOId = stockOutDO.getId();
                // 如果传入数据不为null 则该数据已在表中存在执行保存，若有值则修改
                if (itemDO.getId() == null) {
                    itemDO.setOutId(stockOutDOId);
                    stockOutItemService.save(itemDO);
                    // 库存数量
                    BigDecimal proCount = itemDO.getCount();
                    // 保存库存明细表数据
                    Pair<List<StockItemDO>, List<StockDO>> saveStockDetail = this.saveStockDetail(stockOutDOId, itemDO.getId(),
                            itemDO.getStockId(), proCount, itemDO.getSourceType(), storageType, stockList);
                    stockInfos.add(saveStockDetail);
                    continue;
                }
                BigDecimal proCount = itemDO.getCount();
                // 修改出库明细表数据
                updateItemDOs.add(itemDO);
                // 重新保存库存明细表数据
                Pair<List<StockItemDO>, List<StockDO>> saveStockDetail = this.saveStockDetail(stockOutDOId, itemDO.getId(), itemDO.getStockId(),
                        proCount, itemDO.getSourceType(), storageType, stockList);
                stockInfos.add(saveStockDetail);
            }
            if (updateItemDOs.size() > 0) {
               stockOutItemService.batchUpdate(updateItemDOs);
            }
            if (stockInfos.size() > 0) {
                this.batchSaveStockInfo(stockInfos);
                return R.ok();
            }
        }
        return R.error();
    }
    @Override
    public Map<String, Object> getDetail2(Long id) {
        Map<String, Object> results = Maps.newHashMap();
        List<Map<String, Object>> stockOutItem = new ArrayList<>();
        Map<String, Object> stockOut = new HashMap<>();
        Map<String, Object> param = Maps.newHashMap();
        param.put("id", id);
//        Map<String, Object> byOutId = logisticsService.getByOutId(id);


        List<Map<String, Object>> list = aslBarStockDao.listOutStock(param);
        Map<String, Object> outStock = list.get(0);

        Map<String, Object> item = new HashMap<>();

        //因为是根据前端页面反推属性名称，不确定所以会有相同的value有不同的key
        stockOut.put("crateByName",outStock.get("updateBy"));
        stockOut.put("createTime",outStock.get("outTime"));
        stockOut.put("clientId",outStock.get("clientId"));
        stockOut.put("clientName",outStock.get("clientName"));
        stockOut.put("salesUserName",outStock.get("salesUserName"));
        stockOut.put("salesUser",outStock.get("salesUserId"));
        stockOut.put("createUserName",outStock.get("updateByName"));
        stockOut.put("deptName",outStock.get("deptName"));
        stockOut.put("deptId",outStock.get("deptId"));
        stockOut.put("linkPhone",outStock.get("linkPhone"));
        stockOut.put("linkName",outStock.get("linkName"));
        stockOut.put("operatorName",outStock.get("operatorName"));
        stockOut.put("outCode",outStock.get("sourceCode"));
        stockOut.put("outTime",outStock.get("outTime"));
        stockOut.put("startTime",outStock.get("outTime"));
        stockOut.put("endTime",outStock.get("outTime"));
        stockOut.put("materielName",outStock.get("materielName"));

        item.put("materielSerialNo",outStock.get("materielSerialNo"));
        item.put("unitUomName",outStock.get("unitUomName"));
        item.put("unitPrice",outStock.get("unitPrice"));
        item.put("sellunitprice",outStock.get("sellUnitPrice"));
        item.put("sellamount",outStock.get("sellAmount"));
//        item.put("amount",outStock.get("unitPrice"));
        item.put("salePrice",outStock.get("unitPrice"));
        item.put("batch",outStock.get("batchNo"));
        item.put("warehouse",outStock.get("facName"));
        item.put("warehLocation",outStock.get("locationName"));
        item.put("availableCount",outStock.get("sum"));
        item.put("inheadCode",outStock.get("code"));
        item.put("serialno",outStock.get("materielSerialNo"));
        item.put("materielName",outStock.get("materielName"));
        item.put("specification",outStock.get("specification"));
        item.put("unitName",outStock.get("unitName"));
        item.put("count",outStock.get("sum"));
        item.put("facName",outStock.get("facName"));
        item.put("warehouseName",outStock.get("facName"));
        item.put("warehLocationName",outStock.get("locationName"));
        item.put("locationName",outStock.get("locationName"));
        item.put("sourceCode",outStock.get("sourceCode"));
        item.put("sourceTypeName",outStock.get("sourceTypeName"));
        stockOutItem.add(item);
//            List<Map<String, Object>> stockOutItem = stockOutItemService.listForMap(param);
        results.put("stockOutItem", stockOutItem);
        results.put("stockOut", stockOut);
//            if (stockOutItem.size() > 0) {
//                Map<String,Object> params;
//                for (Map<String, Object> map : stockOutItem) {
//                    params = Maps.newHashMap();
//                    params.put("materielId",map.get("materielId"));
//                    params.put("batch",map.get("batch"));
//                    params.put("locationId",map.get("locationId"));
//                    params.put("facilityId",map.get("facId"));
//                    params.put("isPC",1);
//                    List<Map<String, Object>> stockListForMap = materielService.stockListForMap(params);
//                    BigDecimal count = MathUtils.getBigDecimal(map.get("count"));
//                    if (stockListForMap.size() > 0) {
//                        map.put("availableCount", count.add(MathUtils.getBigDecimal(stockListForMap.get(0).get("availableCount"))));
//                    }else {
//                        map.put("availableCount",count);
//                    }
//                }
//            }
//        }

        return results;
    }
    @Override
    public Map<String, Object> getDetail(Long id) {
        Map<String, Object> results = Maps.newHashMap();
        Map<String, Object> param = Maps.newHashMap();
        param.put("id", id);
        List<Map<String, Object>> maps = this.listForMap(param);
        Map<String, Object> stockOut = maps.size() > 0 ? maps.get(0) : null;
        if (stockOut!=null){
            Map<String, Object> byOutId = logisticsService.getByOutId(id);
            if (byOutId!=null){
                stockOut.putAll(byOutId);
            }
            results.put("stockOut", stockOut);


            List<Map<String, Object>> stockOutItem = stockOutItemService.listForMap(param);
            results.put("stockOutItem", stockOutItem);
            if (stockOutItem.size() > 0) {
                Map<String,Object> params;
                for (Map<String, Object> map : stockOutItem) {
                    params = Maps.newHashMap();
                    params.put("materielId",map.get("materielId"));
                    params.put("batch",map.get("batch"));
                    params.put("locationId",map.get("locationId"));
                    params.put("facilityId",map.get("facId"));
                    params.put("isPC",1);
                    List<Map<String, Object>> stockListForMap = materielService.stockListForMap(params);
                    BigDecimal count = MathUtils.getBigDecimal(map.get("count"));
                    if (stockListForMap.size() > 0) {
                        map.put("availableCount", count.add(MathUtils.getBigDecimal(stockListForMap.get(0).get("availableCount"))));
                    }else {
                        map.put("availableCount",count);
                    }
                }
            }
        }

        return results;
    }

    @Override
    public List<Map<String, Object>> listApi(Map<String, Object> map) {
//        return this.stockOutDao.listApi(map);
        return this.stockOutDao.listApi2(map);
    }

    @Override
    public int countApi(Map<String, Object> map) {
        return this.stockOutDao.countApi(map);
    }

    @Override
    public List<Map<String, Object>> listForMap(Map<String, Object> map) {
        return this.stockOutDao.listForMap(map);
    }

    @Override
    public int countForMap(Map<String, Object> map) {
        return this.stockOutDao.countForMap(map);
    }




    @Override
    public R add(int sign, StockOutDO stockOutDO, String item, DictionaryDO storageType, String uploadAttachments) {
        Map<String, Object> result = Maps.newHashMap();
        List<StockOutItemDO> itemDOs = JSON.parseArray(item, StockOutItemDO.class);
        itemDOs.forEach(e -> {
            if (e.getAmount() == null) {
                e.setAmount(BigDecimal.ZERO);
            }
            if (e.getUnitPrice() == null) {
                e.setUnitPrice(BigDecimal.ZERO);
            }
        });

        // 是否为二维码出库
//        if (itemDOs.get(0).getQrcodeId() != null) {
//            return this.addByQrcodeId(stockOutDO,itemDOs,storageType);
//        }
        List<StockDO> stockList = new ArrayList<>();

        //TODO  同步金蝶不做库存校验   sign 是否同步 1是 0否   -->(前端做了可执行数量的校验)
        //校验是否有库存
        if (!Objects.equal(1, sign)) {
            // 将JSON 解析出来以库存ID 数组 和 数量作为验证条件
            List<Map<String, Object>> params = this.stockListParam(itemDOs);
            // 获取库存明细表中保存的库位id数组
            List<Long> stockIds = getAllStockIds(params);
            if (checkInsert(params, stockIds)) {
                return R.error(messageSourceHandler.getMessage("stock.count.error", null));
            }
            // 库存无误执行下面保存操作
            stockList = materielService.stockList(stockIds);
        }
        // 设置出库类型
        Long storageTypeId = storageType.getId();
        stockOutDO.setOutboundType(storageTypeId);
        // 设置审核状态为待审核
        stockOutDO.setAuditSign(Constant.WAIT_AUDIT);
        // 设置出库单据号
        String value = storageType.getValue();

        //获取编号
        //TODO  同步金蝶后编码使用金蝶编码
        if (!Objects.equal(1, sign)) {
            String maxNo = DateFormatUtil.getWorkOrderno(value);
            Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
            param.put("maxNo", maxNo);
            param.put("offset", 0);
            param.put("limit", 1);
            List<StockOutDO> list = this.list(param);
            String taskNo = null;
            if (!CollectionUtils.isEmpty(list)) {
                taskNo = list.get(0).getOutCode();
            }
            stockOutDO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));
            stockOutDO.setCreateBy(ShiroUtils.getUserId());
            stockOutDO.setOperator(ShiroUtils.getUserId());
        }
        //TODO  UUId 同步的唯一标记
        itemDOs.stream().forEach(aa -> {
            UUID uuid = UUID.randomUUID();
            aa.setUuid(uuid.toString());
        });
        //TODO  添加同步金蝶逻辑   扫码出库同步金蝶直接设置已审核 不管库存
        //TODO  添加同步金蝶逻辑
        if (java.util.Objects.equals(sign, 1)) {
            Pair<Boolean, String> pair = kisBillOfPushService.masterSwitchForOutboundSync(storageTypeId.intValue(), itemDOs, stockOutDO);
            if (!pair.getLeft()) {
                return R.error(pair.getRight());
            }
        }
        // 保存主表数据
        int count = 0;
        if(java.util.Objects.equals(sign,1) && java.util.Objects.nonNull(stockOutDO.getId())){
            count=  stockOutDao.saveWithId(stockOutDO);
        }else{
            count=  stockOutDao.save(stockOutDO);
        }
        Long stockOutId = stockOutDO.getId();
        result.put("id", stockOutId);
//        if (storageType.getId() != null && storageType.getId().equals(ConstantForGYL.XSCK) && StringUtils.isNotBlank(uploadAttachments)) {
//            contentAssocService.saveList(stockOutId, uploadAttachments, ConstantForGYL.SALES_STOCK_FILE, true);
//        }
        if (count > 0) {
            // 保存出库产品
            List<StockOutItemDO> saveItemDOs = Lists.newArrayList();
            for (StockOutItemDO obj : itemDOs) {
                obj.setOutId(stockOutId);
                saveItemDOs.add(obj);
            }
            // 批量更新子表数据
            int saveItemCount = stockOutItemService.batchInsert(saveItemDOs);

            //TODO   同步金蝶库存取金蝶库存 gyhl不管理库存
            // 子表数据保存成功保存库存信息
            if (saveItemCount == saveItemDOs.size() && !java.util.Objects.equals(sign,1)) {
                List<Pair<List<StockItemDO>, List<StockDO>>> stockInfos = Lists.newArrayList();
                for (StockOutItemDO itemDO : itemDOs) {
                    BigDecimal subProCount;
                    BigDecimal proCount = itemDO.getCount();
                    if (ConstantForGYL.CGTH.equals(storageTypeId)) {
                        //采购退货属于出库
                        subProCount = itemDO.getBackCount();
                    } else {
                        subProCount = itemDO.getOutCount();
                    }
                    // 保存库存明细数据并设置为未生效
                    Pair<List<StockItemDO>, List<StockDO>> saveStockDetail = this.saveStockDetail(stockOutId, itemDO.getId(),
                            itemDO.getStockId(), proCount, itemDO.getSourceType(), storageTypeId,
                            stockList);
                    if (saveStockDetail != null) {
                        stockInfos.add(saveStockDetail);
                    }
                }
                // 保存库存明细
                this.batchSaveStockInfo(stockInfos);
                return R.ok(result);
            }
            // TODO 审核 并 执行反写投料单
            if (Objects.equal(1, sign)) {
//                consumingStockOutService.writeFeedingCount(stockOutId, true);
//                consumingStockOutService.auditConsumingStockOut(stockOutId, ConstantForGYL.LYCK);
                retrographyAndAudit(stockOutId,itemDOs,storageTypeId.intValue(),false);
            }
            return R.ok(result);
        }
        //TODO
        return R.ok(result);
    }
//    /**
//     * 扫码出库
//     */
//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public R barOutStock(AslBarStockDO aslBarStockVO) {
//        //1、重复扫码校验
//        if (ObjectUtils.isEmpty(aslBarStockVO) || ObjectUtils.isEmpty(aslBarStockVO.getTrayIds()) || aslBarStockVO.getTrayIds().size()<=0){
//            return R.error("未选择出库货物！");
//        }
//        if (ObjectUtils.isEmpty(aslBarStockVO.getDeliveryId())){
//            return R.error("未选择发货通知！");
//        }
//        Map<String, Object> param2 = Maps.newHashMapWithExpectedSize(3);
//        param2.put("trayIds", aslBarStockVO.getTrayIds());
//        param2.put("status", 0);
//        AslBarStockDO aslBarStock = null;
//        BigDecimal total = null;
//        synchronized(this){
//
//            List<Map<String, Object>> inStocks = aslBarStockDao.listInStock(param2);
//            List<Long> trayIds = inStocks.stream().map(inStock -> ObjectUtils.isEmpty(inStock.get("trayId")) ? null : (Long)inStock.get("trayId") ).distinct().collect(Collectors.toList());
//            if (ObjectUtils.isEmpty(inStocks) || inStocks.size()!= aslBarStockVO.getTrayIds().size()){
//                return R.error("存在已出库或未入库的托盘，请重新扫码！");
//            }
//            BigDecimal sum = inStocks.stream().filter(tr -> tr.containsKey("sum") && !ObjectUtils.isEmpty(tr.get("sum"))).map(tr -> new BigDecimal(tr.get("sum").toString())).reduce(BigDecimal.ZERO, BigDecimal::add);
//            total = sum;
//            Long productId = ObjectUtils.isEmpty(inStocks.get(0).get("productId")) ? null : (Long)inStocks.get(0).get("productId");
//            //库位或仓库主键不为空时，检查主键是否正确
////            if ((!ObjectUtils.isEmpty(aslBarStockVO.getWarehouse()) && !aslBarStockVO.getWarehouse().equals(aslBarStock.getWarehouse()))
////                    || (!ObjectUtils.isEmpty(aslBarStockVO.getWarehLocation()) && !aslBarStockVO.getWarehLocation().equals(aslBarStock.getWarehLocation()))){
////                return R.error("该托盘货物不在该库位中");
////            }
//            //2、出库
//            DictionaryDO dictionaryDO = dictionaryService.get(ConstantForGYL.XSCK);
//            String maxNo = DateFormatUtil.getWorkOrderno(dictionaryDO.getValue());
//            Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
//            param.put("outStock", 0);
//            param.put("offset", 0);
//            param.put("limit", 1);
//            List<AslBarStockDO> list = aslBarStockDao.list(param);
//            String taskNo = null;
//            if (!CollectionUtils.isEmpty(list)) {
//                taskNo = list.get(0).getOutCode();
//            }
//            aslBarStockVO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));
//            aslBarStockVO.setDept(ShiroUtils.getUser().getDeptId());
//            aslBarStockVO.setOperson(ShiroUtils.getUserId());
//            aslBarStockVO.setStatus(1);
//            aslBarStockVO.setDeliveryId(aslBarStockVO.getDeliveryId());
//            aslBarStockVO.setOutType(dictionaryDO.getId());
//
////            {
////            itemDO.setSupportUom(aslBarStockVO.getSupportUom());//前端传参
////            itemDO.setUnitPrice(BigDecimal.ZERO);//暂时给了0
////            itemDO.setQrcodeId(aslBarStock.getTrayId());
////            itemDO.setStockId(aslBarStock.getId().toString());
////            itemDO.setOutCount(new BigDecimal(1));
////            itemDO.setAmount(BigDecimal.ZERO);//暂时给了0
////            stockOutDO.setOutCode(aslBarStock.getOutCode());
////            stockOutDO.setOutboundType(aslBarStock.getOutType());
////            stockOutDO.setSalesUser(aslBarStockVO.getSalesUser());
////            stockOutDO.setDeptId(aslBarStockVO.getDeptId());
////            stockOutDO.setSalesType(0); // 选择0现销/1赊销
////            stockOutDO.setOutTime(new Date());
////            stockOutDO.setCreateBy(ShiroUtils.getUserId());
////            stockOutDO.setCreateTime(new Date());
////            stockOutDO.setUpdateBy(ShiroUtils.getUserId());
////            stockOutDO.setUpdateTime(new Date());
////            stockOutDO.setAuditSign(Constant.OK_AUDITED);
////            stockOutDO.setDelFlag(0);// 删除状态是0还是1
////            }
//
//            // todo 3、同步金蝶 传递桶的信息
//            StockOutDO stockOutDO = new StockOutDO();
//            stockOutDO.setOperator(ShiroUtils.getUserId());
//            stockOutDO.setClientId(aslBarStockVO.getClientId());
//            List<StockOutItemDO> itemDOs = new ArrayList<>();
//            aslBarStockVO.getTrayIds().forEach(ty ->{
//                StockOutItemDO itemDO = new StockOutItemDO();
//                UUID uuid = UUID.randomUUID();
//                itemDO.setUuid(uuid.toString());
//                itemDO.setMaterielId(productId);
//                itemDO.setCount(sum);
//                itemDO.setSourceCode(aslBarStockVO.getDeliveryCode());
//                itemDO.setSourceInteridEntryid(ObjectUtils.isEmpty(aslBarStockVO.getNoticeidEntryId()) ? "" : aslBarStockVO.getNoticeidEntryId());
//                itemDO.setBatch(aslBarStockVO.getBatchNo());
//                itemDO.setWarehouse(aslBarStockVO.getWarehouse());
//                itemDO.setWarehLocation(aslBarStockVO.getWarehLocation());
//                itemDOs.add(itemDO);
//            });
//
//            Pair<Boolean, String> pair = kisBillOfPushService.masterSwitchForOutboundSync(aslBarStockVO.getOutType().intValue(), itemDOs, stockOutDO);
//            if (!pair.getLeft()) {
//                return R.error(pair.getRight());
//            }
//            aslBarStockVO.setOutId(stockOutDO.getId());
//
//            aslBarStockDao.save(aslBarStockVO);
//            ArrayList<AslTrayStockDO> trayStockDOS = new ArrayList<>();
//            trayIds.forEach(trayId ->{
//                AslTrayStockDO trayStockDO = new AslTrayStockDO();
//                trayStockDO.setTrayId(trayId);
//                trayStockDO.setOutStockId(aslBarStockVO.getId());
//                trayStockDO.setStatus(1);
//                trayStockDO.setUpdateTime(new Date());
//                trayStockDO.setUpdateBy(ShiroUtils.getUserId());
//                trayStockDOS.add(trayStockDO);
//            });
//            aslBarStockDao.updateTrayStock(trayStockDOS);
//            //todo 修改asl_tray_stock
//        }
//
//        //4、修改发货通知详细表
//        //todo 出入库及同步金蝶的单位是托还是桶
//        Map<Long, BigDecimal> countOfdelivery = new HashMap<>();
//        countOfdelivery.put(aslBarStockVO.getDeliveryId(),total);
//        iDeliveryNoteService.retrographyExecutableCount(countOfdelivery);
//
//        //5、记录发货单信息
////        logisticsService.save(logistics);
//        return R.ok("出库成功");
//    }

    /**限制频繁调用*/
    public Pair<Boolean,String> limitFrequentCalls(String redisKey){
        String key = ShiroUtils.getUserId() + redisKey;
        Object value = redisTemplate.opsForValue().get(key);
        if (java.util.Objects.nonNull(value)) {
            Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            return Pair.of(true,"禁止频繁操作同步！请 "+expire +"秒后重试");
        }
        //同一个登录人5秒内只可以操作一次
        redisTemplate.opsForValue().set(key, "Temporarily out of sync", 10l, TimeUnit.SECONDS);
        return Pair.of(false,"");
    }
    /**
     * 扫码出库
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized R barOutStock(StockOutVO stockOutVO) {
        //校验 + 限制频繁调用
        Pair<Boolean, String> limitPair = this.limitFrequentCalls(""  + ShiroUtils.getUserId());
        if (limitPair.getLeft()) {
            return R.error(limitPair.getRight());
        }
        List<StockInItemVO> trayVOS = new ArrayList<>();
        trayVOS = stockOutVO.getStockInItemVOS();
        Long warehouse = stockOutVO.getWarehouse();
        if (ObjectUtils.isEmpty(trayVOS)){
            return R.error("未选择托盘码，请重新扫码！");
        }
        List<Long> trayVOSIds = trayVOS.stream().map(StockInItemVO::getTrayId).collect(Collectors.toList());
        Map<String, Object> param2 = Maps.newHashMapWithExpectedSize(3);
        param2.put("trayIds", trayVOSIds);
        List<Map<String, Object>> inStockDOS = aslBarStockDao.listInStock(param2);
        HashMap<String, Object> deliveryMap = new HashMap<>();
        deliveryMap.put("id",stockOutVO.getDeliveryId());
        List<Map<String, Object>> deliveryNoteList = new ArrayList<>();
        try {
            deliveryNoteList = deliveryNoteService.getDeliveryNoteList(deliveryMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //1、判断托盘码是否均已入库
        if (ObjectUtils.isEmpty(inStockDOS) || inStockDOS.size()!= trayVOSIds.size()){
            return R.error("存在未入库的托盘，请重新扫码！");
        }
        //2、判断托盘码中的数量是否足够、库位是否正确
        ArrayList<AslTrayStockDO> trayStockDOS = new ArrayList<>();
        AslBarStockDO aslBarStockVO = new AslBarStockDO();
        ArrayList<AslBarCodeTrayDO> trayDOS = new ArrayList<>();
        StockOutDO stockOutDO = new StockOutDO();
        stockOutDO.setOperator(ShiroUtils.getUserId());
        if (!ObjectUtils.isEmpty(deliveryNoteList) && deliveryNoteList.get(0).containsKey("harvestAddress")) stockOutDO.setConsigneeAdd(deliveryNoteList.get(0).get("harvestAddress").toString());
        stockOutDO.setClientId(stockOutVO.getClientId());
        List<StockOutItemDO> itemDOs = new ArrayList<>();Map<Long, StockInItemVO> trayVOSMapByTrayId = trayVOS.stream().collect(Collectors.toMap(StockInItemVO::getTrayId, v -> v, (v1, v2) -> v1));
        for (int c =0; c< inStockDOS.size(); c++){
            Map<String, Object> inStockDO = inStockDOS.get(c);
            Long trayDOId = (Long) inStockDO.get("trayId");
            StockInItemVO trayVO = trayVOSMapByTrayId.get(trayDOId);
            if (ObjectUtil.isEmpty(trayVO)) {
                log.error("====================出库托盘码匹配错误，trayId：{}",trayDOId);
                log.error("====================出库托盘码匹配错误，trayVOSIds：{}",trayVOSIds);
                return R.error("托盘码错误，请联系管理员！");
            }
            if (!warehouse.equals(new Long(inStockDO.get("facilityId").toString()) )) {
                log.error("====================出库托盘码库位错误，warehouse：{}",warehouse);
                log.error("====================出库托盘码库位错误，facilityId：{}",inStockDO.get("facilityId"));
                log.error("====================出库托盘码库位错误，trayId：{}",trayDOId);
                log.error("====================出库托盘码库位错误，trayVOSIds：{}",trayVOSIds);
                String batchNos = inStockDO.get("batchNos").toString();
                String msg = new String();
                msg = "托盘码" + batchNos + "不在该库位";
                return R.error(msg);
            }
            BigDecimal nowOutSum = trayVO.getNum();
            BigDecimal alreadyOutSum = ObjectUtil.isEmpty(inStockDO.get("OutSum")) ? BigDecimal.ONE : new BigDecimal(inStockDO.get("OutSum").toString());
            BigDecimal alreadyInSum = ObjectUtil.isEmpty(inStockDO.get("InSum")) ? BigDecimal.ONE : new BigDecimal(inStockDO.get("InSum").toString());
            log.error("====================nowOutSum，nowOutSum：{}",nowOutSum);
            log.error("====================alreadyOutSum，alreadyOutSum：{}",alreadyOutSum);
            log.error("====================alreadyInSum，alreadyInSum：{}",alreadyInSum);
            if (alreadyInSum.compareTo(nowOutSum.add(alreadyOutSum)) < 0) {
                log.error("====================出库托盘码桶数量不足，trayId：{}",trayDOId);
                log.error("====================出库托盘码桶数量不足，trayVOSIds：{}",trayVOSIds);
                log.error("====================出库托盘码桶数量不足，nowOutSum：{}",nowOutSum);
                log.error("====================出库托盘码桶数量不足，alreadyOutSum：{}",alreadyOutSum);
                log.error("====================出库托盘码桶数量不足，alreadyInSum：{}",alreadyInSum);
                String batchNos = inStockDO.get("batchNos").toString();
                String msg = new String();
                msg = "托盘码" + batchNos + "中剩下的桶数量不足";
                return R.error(msg);
            }

            AslTrayStockDO trayStockDO = new AslTrayStockDO();
            trayStockDO.setTrayId(trayDOId);
            trayStockDO.setStatus(1);
            trayStockDO.setOutSum(nowOutSum.add(alreadyOutSum));
            trayStockDO.setUpdateTime(new Date());
            trayStockDO.setUpdateBy(ShiroUtils.getUserId());
            trayStockDOS.add(trayStockDO);

            AslBarCodeTrayDO trayDO = new AslBarCodeTrayDO();
            String nowTotal = alreadyInSum.subtract(alreadyOutSum).subtract(nowOutSum).toString();
            trayDO.setSum(new Integer(nowTotal));
            trayDO.setId(trayDOId);
            trayDOS.add(trayDO);

            aslBarStockVO.setSourceCode(stockOutVO.getDeliveryCode());
            aslBarStockVO.setProductId(ObjectUtils.isEmpty(inStockDO.get("productId")) ? null : (Long)inStockDO.get("productId"));
            aslBarStockVO.setProductName(ObjectUtils.isEmpty(inStockDO.get("materielName")) ? null : inStockDO.get("materielName").toString());
            aslBarStockVO.setProductModel(ObjectUtils.isEmpty(inStockDO.get("specification")) ? null : inStockDO.get("specification").toString());
            StockOutItemDO outBodyDO = new StockOutItemDO();
            UUID uuid = UUID.randomUUID();
            outBodyDO.setUuid(uuid.toString());
            outBodyDO.setSourceInteridEntryid(stockOutVO.getNoticeidEntryId());
            outBodyDO.setBatch(ObjectUtils.isEmpty(inStockDO.get("batchNos")) ? "" : inStockDO.get("batchNos").toString());
            outBodyDO.setSourceCode(stockOutVO.getDeliveryCode());//这是四个桶其中之一的生产任务数据
            outBodyDO.setWarehouse(stockOutVO.getWarehouse());
            outBodyDO.setWarehLocation(stockOutVO.getWarehLocation());
            outBodyDO.setMaterielId(ObjectUtils.isEmpty(inStockDO.get("productId")) ? null : (Long)inStockDO.get("productId"));
            outBodyDO.setCount(nowOutSum);
            itemDOs.add(outBodyDO);
        }



        // 3、同步金蝶 传递桶的信息
        DictionaryDO dictionaryDO = dictionaryService.get(ConstantForGYL.XSCK);
        String maxNo = DateFormatUtil.getWorkOrderno(dictionaryDO.getValue());
        Pair<Boolean, String> pair = kisBillOfPushService.masterSwitchForOutboundSync(dictionaryDO.getId().intValue(), itemDOs, stockOutDO);
        if (!pair.getLeft()) {
            return R.error(pair.getRight());
        }

        //4、更新出库记录
        Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
        param.put("outStock", 0);
        param.put("offset", 0);
        param.put("limit", 1);
        List<AslBarStockDO> list = aslBarStockDao.list(param);
        String taskNo = null;
        if (!CollectionUtils.isEmpty(list)) {
            taskNo = list.get(0).getOutCode();
        }
        aslBarStockVO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));
        aslBarStockVO.setDept(ShiroUtils.getUser().getDeptId());
        aslBarStockVO.setOperson(ShiroUtils.getUserId());
        aslBarStockVO.setStatus(1);
        aslBarStockVO.setDeliveryId(stockOutVO.getDeliveryId());
        aslBarStockVO.setOutType(dictionaryDO.getId());
        aslBarStockVO.setOutId(stockOutDO.getId());
        aslBarStockVO.setBatchNo(stockOutVO.getBatchNos());
        aslBarStockVO.setWarehouse(stockOutVO.getWarehouse());
        aslBarStockVO.setWarehLocation(stockOutVO.getWarehLocation());
        aslBarStockVO.setSum(trayVOSIds.size());
        aslBarStockVO.setSalesUser(stockOutVO.getSalesUser());
        aslBarStockVO.setClientId(stockOutVO.getClientId());
        aslBarStockVO.setDeptId(stockOutVO.getDeptId());
        aslBarStockVO.setDeliveryId(stockOutVO.getDeliveryId());
        aslBarStockVO.setDeliveryCode(stockOutVO.getDeliveryCode());
        aslBarStockDao.save(aslBarStockVO);
        trayStockDOS.forEach(trayItem ->{
            trayItem.setOutStockId(aslBarStockVO.getId());
        });
        aslBarStockDao.updateTrayStock(trayStockDOS);
        //更新托盘中的剩余桶数量
        aslBarCodeService.batchUpdateTrays(trayDOS);
        //5、修改发货通知详细表
        Map<Long, BigDecimal> countOfdelivery = new HashMap<>();
        BigDecimal total = trayVOS.stream().map(StockInItemVO::getNum).reduce(BigDecimal.ZERO, BigDecimal::add);
        countOfdelivery.put(aslBarStockVO.getDeliveryId(),total);
        iDeliveryNoteService.retrographyExecutableCount(countOfdelivery);

        return R.ok("出库成功");
    }
    @Override
    public R addByTray(int sign, StockOutDO stockOutDO, String item, DictionaryDO storageType, String uploadAttachments) {
        Map<String, Object> result = Maps.newHashMap();
        List<StockOutItemDO> itemDOs = JSON.parseArray(item, StockOutItemDO.class);
        if (ObjectUtils.isEmpty(itemDOs) || itemDOs.size() <= 0){
            return R.error("未选择出库货物");
        }
        itemDOs.forEach(e -> {
            if (e.getAmount() == null) {
                e.setAmount(BigDecimal.ZERO);
            }
            if (e.getUnitPrice() == null) {
                e.setUnitPrice(BigDecimal.ZERO);
            }
        });

        //二维码出库
        StockDO stockDO = stockService.getByQrCode(itemDOs.get(0).getQrcodeId());
        if (ObjectUtils.isEmpty(stockDO) || ObjectUtils.isEmpty(stockDO.getCount())){
            return R.error("该货物还未入库");
        }
        if (stockDO.getCount().compareTo(new BigDecimal(1)) < 0){
            return R.error("该货物已出库");
        }
        this.addByBarcodeId(stockOutDO,itemDOs,stockDO,storageType);
        return R.ok();
//        List<StockDO> stockList = new ArrayList<>();
//
//        //TODO  同步金蝶不做库存校验   sign 是否同步 1是 0否   -->(前端做了可执行数量的校验)
//        //校验是否有库存
////        if (!Objects.equal(1, sign)) {
////            // 将JSON 解析出来以库存ID 数组 和 数量作为验证条件
////            List<Map<String, Object>> params = this.stockListParam(itemDOs);
////            // 获取库存明细表中保存的库位id数组
////            List<Long> stockIds = getAllStockIds(params);
////            if (checkInsert(params, stockIds)) {
////                return R.error(messageSourceHandler.getMessage("stock.count.error", null));
////            }
////            // 库存无误执行下面保存操作
////            stockList = materielService.stockList(stockIds);
////        }
//        // 设置出库类型
//        Long storageTypeId = storageType.getId();
//        stockOutDO.setOutboundType(storageTypeId);
//        // 设置审核状态为待审核
//        stockOutDO.setAuditSign(Constant.WAIT_AUDIT);
//        // 设置出库单据号
//        String value = storageType.getValue();
//
//        //获取编号
//        //TODO  同步金蝶后编码使用金蝶编码
//        if (!Objects.equal(1, sign)) {
//            String maxNo = DateFormatUtil.getWorkOrderno(value);
//            Map<String, Object> param = Maps.newHashMapWithExpectedSize(3);
//            param.put("maxNo", maxNo);
//            param.put("offset", 0);
//            param.put("limit", 1);
//            List<StockOutDO> list = this.list(param);
//            String taskNo = null;
//            if (!CollectionUtils.isEmpty(list)) {
//                taskNo = list.get(0).getOutCode();
//            }
//            stockOutDO.setOutCode(DateFormatUtil.getWorkOrderno(maxNo, taskNo));
//            stockOutDO.setCreateBy(ShiroUtils.getUserId());
//            stockOutDO.setOperator(ShiroUtils.getUserId());
//        }
////        //TODO  UUId 同步的唯一标记
////        itemDOs.stream().forEach(aa -> {
////            UUID uuid = UUID.randomUUID();
////            aa.setUuid(uuid.toString());
////        });
////        //TODO  添加同步金蝶逻辑   扫码出库同步金蝶直接设置已审核 不管库存
////        //TODO  添加同步金蝶逻辑
////        if (java.util.Objects.equals(sign, 1)) {
////            Pair<Boolean, String> pair = kisBillOfPushService.masterSwitchForOutboundSync(storageTypeId.intValue(), itemDOs, stockOutDO);
////            if (!pair.getLeft()) {
////                return R.error(pair.getRight());
////            }
////        }
//        // 保存主表数据
//        int count = 0;
//        if(java.util.Objects.equals(sign,1) && java.util.Objects.nonNull(stockOutDO.getId())){
//            count=  stockOutDao.saveWithId(stockOutDO);
//        }else{
//            count=  stockOutDao.save(stockOutDO);
//        }
//        Long stockOutId = stockOutDO.getId();
//        result.put("id", stockOutId);
////        if (storageType.getId() != null && storageType.getId().equals(ConstantForGYL.XSCK) && StringUtils.isNotBlank(uploadAttachments)) {
////            contentAssocService.saveList(stockOutId, uploadAttachments, ConstantForGYL.SALES_STOCK_FILE, true);
////        }
//        if (count > 0) {
//            // 保存出库产品
//            List<StockOutItemDO> saveItemDOs = Lists.newArrayList();
//            for (StockOutItemDO obj : itemDOs) {
//                obj.setOutId(stockOutId);
//                saveItemDOs.add(obj);
//            }
//            // 批量更新子表数据
//            int saveItemCount = stockOutItemService.batchInsert(saveItemDOs);
//
//            //TODO   同步金蝶库存取金蝶库存 gyhl不管理库存
//            // 子表数据保存成功保存库存信息
//            if (saveItemCount == saveItemDOs.size() && !java.util.Objects.equals(sign,1)) {
//                List<Pair<List<StockItemDO>, List<StockDO>>> stockInfos = Lists.newArrayList();
//                for (StockOutItemDO itemDO : itemDOs) {
//                    BigDecimal subProCount;
//                    BigDecimal proCount = itemDO.getCount();
//                    if (ConstantForGYL.CGTH.equals(storageTypeId)) {
//                        //采购退货属于出库
//                        subProCount = itemDO.getBackCount();
//                    } else {
//                        subProCount = itemDO.getOutCount();
//                    }
//                    // 保存库存明细数据并设置为未生效
//                    Pair<List<StockItemDO>, List<StockDO>> saveStockDetail = this.saveStockDetail(stockOutId, itemDO.getId(),
//                            itemDO.getStockId(), proCount, itemDO.getSourceType(), storageTypeId,
//                            stockList);
//                    if (saveStockDetail != null) {
//                        stockInfos.add(saveStockDetail);
//                    }
//                }
//                // 保存库存明细
//                this.batchSaveStockInfo(stockInfos);
//                return R.ok(result);
//            }
//            // TODO 审核 并 执行反写投料单
//            if (Objects.equal(1, sign)) {
////                consumingStockOutService.writeFeedingCount(stockOutId, true);
////                consumingStockOutService.auditConsumingStockOut(stockOutId, ConstantForGYL.LYCK);
//                retrographyAndAudit(stockOutId,itemDOs,storageTypeId.intValue(),false);
//            }
//            return R.ok(result);
//        }
//        //TODO
//        return R.ok(result);
    }

    @Autowired
    private IDeliveryNoteService iDeliveryNoteService;
    //执行反写及审核
    public void retrographyAndAudit(Long id , List<StockOutItemDO> itemDOs, int type,Boolean sign ){
        Map<Long, BigDecimal> countOfThis = itemDOs.stream().filter(e -> e.getSourceId() != null)
                .collect(Collectors.toMap(StockOutItemDO::getSourceId, StockOutItemDO::getCount, BigDecimal::add));
        switch (type){
            case 188: //销售出库    反写 发货通知，不管库存（即不审核销售出库），只校验发货通知可执行数量
                iDeliveryNoteService.retrographyExecutableCount(countOfThis);
                break;
            case 193: //生产领用
                consumingStockOutService.writeFeedingCount(id, true);
                consumingStockOutService.auditConsumingStockOut(id, ConstantForGYL.LYCK);
                break;
            case 259: //采购退货

                break;
            default:
                break;
        }
    }
}
